package asteroids.model;
import be.kuleuven.cs.som.annotate.*;
import asteroids.CollisionListener;
import asteroids.Util;
import asteroids.model.Vector;


/**
 * 
 * @invar	  The coordinates of each entity must be valid.
 * 			| isValidCoordinate(getXCoordinate()) &&
 * 			| isValidCoordinate(getYCoordinate())
 * @invar     The velocity according to the x and y axis must be valid.
 * 			| isValidVelocity(getXVelocity()) && isValidVelocity(getYVelocity)		
 * @invar 	  The total velocity of each entity is less than getMaxSpeed().
 * 		 	| isValidVelocity((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2))))
 * @invar 	  The radius of each entity is valid.
 * 		 	| isValidRadius(getRadius());
 * @invar     If this entity has world then it is inside bounds.
 * 			| if(hasWorld())
 * 			|	 then (getWorld().isInsideBounds(this)) 
 * 
 * @version 0.3
 * @author  Tandan Sunil, Van Deun Luca
 *
 */
public abstract class Entity {
	
	
	/**
	 * Initialize this new entity with given coordinates and speed according to x- and y- axes, radius and direction.
	 *  
	 * @param  xCoordinate 
	 * 		   The x-coordinate for this new entity in kilometers.
	 * @param  yCoordinate
	 * 		   The y-coordinate for this new entity in kilometers.
	 * @param  xVelocity
	 * 		   The initial velocity for this new entity according to the x-axis in kilometers/second.
	 * @param  yVelocity
	 * 	       The initial velocity for this new entity according to the y-axis in kilometers/second.
	 * @param  radius
	 * 		   The radius for this new circular entity in kilometers. 
	 * @param  mass
	 * 		   The mass of this new entity in kilogram.		   
	 * @pre	   The given radius must be a valid radius for a entity.
	 * 		 | isValidRadius(radius)
	 * @pre	   The total speed of this new entity must be valid.
	 * 	 	 | isValidVelocity((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2)))) 
	 * @post   The new entity has the given x-coordinate.
	 * 		 | new.getXCoordinate() == xCoordinate
	 * @post   The new entity has the given y-coordinate.
	 * 		 | new.getYCoordinate() == yCoordinate
	 * @post   The new entity has the given radius.
	 * 		 | new.getRadius() == radius
	 * @effect The given velocities are set as the new velocities of this new entity.
	 * 		 | setVelocity(xVelocity,yVelocity)
	 * @throws IllegalArgumentException
	 * 		   The given radius is invalid.
	 * 		 | ! isValidRadius(radius)
	 * @throws IllegalArgumentException
	 * 		   The given coordinate is invalid.
	 * 		 | ! isValidCoordinate(xCoordinate) || ! isValidCoordinate(yCoordinate)
	 *
	 */
	public Entity(double xCoordinate, double yCoordinate, double xVelocity,
			double yVelocity, double radius, double mass) throws IllegalArgumentException{
			setMaxSpeed(SPEED_OF_LIGHT);
			if(! isValidRadius(radius))
				throw new IllegalArgumentException("Not a valid radius.");
			setCoordinates(xCoordinate,yCoordinate);
			setVelocity(xVelocity,yVelocity);
			this.radius = radius;
			if(!isValidMass(mass))
				throw new IllegalArgumentException("Not a valid mass");
			this.mass= mass;
	}

	/**
	 * Initialize this new entity with given coordinates and speed according to x- and y- axes, radius, direction,mass and maximum speed.
	 *  
	 * @param  xCoordinate 
	 * 		   The x-coordinate for this new entity in kilometers.
	 * @param  yCoordinate
	 * 		   The y-coordinate for this new entity in kilometers.
	 * @param  xVelocity
	 * 		   The initial velocity for this new entity according to the x-axis in kilometers/second.
	 * @param  yVelocity
	 * 	       The initial velocity for this new entity according to the y-axis in kilometers/second.
	 * @param  radius
	 * 		   The radius for this new circular entity in kilometers. 
	 * @param  maxSpeed
	 * 		   The maximum speed for this new entity.
	 * @param  mass
	 * 		   The mass of this new entity in kilogram.
	 * @pre	   The given radius must be a valid radius for a entity.
	 * 		 | isValidRadius(radius)
//	 * @pre	   The total speed of this new entity must be valid.
//	 * 	 	 | isValidVelocity((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2)))) 
	 * @post   The new entity has the given x-coordinate.
	 * 		 | new.getXCoordinate() == xCoordinate
	 * @post   The new entity has the given y-coordinate.
	 * 		 | new.getYCoordinate() == yCoordinate
	 * @post   The new entity has the given radius.
	 * 		 | new.getRadius() == radius
	 * @post   The getMaxSpeed() of the new entity is equal to the given maxSpeed if it is valid.
	 * 		 | if(maxSpeed > 0 && maxSpeed <= SPEED_OF_LIGHT)
	 * 		 |		then new.getMaxSpeed() == maxSpeed
	 * @post   If maxSpeed is greater than the SPEED_OF_LIGHT, or smaller than or equal to 0, or not a number 
	 * 		   then getMaxSpeed() is equal to SPEED_OF_LIGHT.
	 * 		  | if (maxSpeed > SPEED_OF_LIGHT || maxSpeed <= 0 || Double.isNaN(maxSpeed))
	 * 		  |		then new.getMaxSpeed() == SPEED_OF_LIGHT
	 * @effect The given velocities are set as the new velocities of this new entity.
	 * 		 | setVelocity(xVelocity,yVelocity)
	 * @throws IllegalArgumentException
	 * 		   The given radius is invalid.
	 * 		 | ! isValidRadius(radius)
	 * @throws IllegalArgumentException
	 * 		   The given coordinate is invalid.
	 * 		 | ! isValidCoordinate(xCoordinate) || ! isValidCoordinate(yCoordinate)
	 */
	public Entity(double xCoordinate, double yCoordinate, double xVelocity,
			double yVelocity, double radius,double mass,double maxSpeed) throws IllegalArgumentException{
			this(xCoordinate,yCoordinate,xVelocity,yVelocity,radius,mass);
			setMaxSpeed(maxSpeed);
			setVelocity(xVelocity,yVelocity);
	}




	//////////
	// MASS //
	//////////

	private final double mass;

	/**
	 * Returns the mass of this entity.
	 * 
	 */
	@Basic
	public double getMass(){
		return mass;
	}

	/**
	 * Checks whether the given mass is valid.
	 * @param   mass
	 * 			The mass to be checked.
	 * 
	 * @return	Returns true if mass lies between 0 (excluded) and Double.MAX_VALUE (included);
	 *        |	result == (mass > 0 && mass <= Double.MAX_VALUE)
	 *       
	 */
	public static boolean isValidMass(double mass){
		if(Double.isNaN(mass))
			return false;
		if(mass > 0 && mass <= Double.MAX_VALUE )
			return true;
		return false;
	}





	/////////////////
	// COORDINATES //
	/////////////////

	private Vector coordinateVector;

	/**
	 * Returns the x-coordinate of this entity in kilometers.
	 * 
	 */
	@Basic
	public double getXCoordinate(){
		return coordinateVector.getXComponent();
	}

	/**
	 * Returns the y-coordinate of this entity in kilometers.
	 * 
	 */
	@Basic
	public double getYCoordinate(){
		return coordinateVector.getYComponent();
	}
	
	/**
	 * Changes the coordinates of this entity.
	 * @param xCoordinate
	 * 		| The x-coordinate to be set.
	 * @param yCoordinate
	 * 		| The y-coordinate to be set.
	 * @post  ...
	 * 		| new.getXCoordinate() == xCoordinate
	 * 		| new.getYCoordinate() == yCoordinate
	 * 		
	 * @throws IllegalArgumentException
	 * 		| !Vector.isValidComponent(xCoordinate) || !Vector.isValidComponent(yCoordinate)
	 */
	@Raw //Because it does not check the dimension of the world this entity lies in.
	protected void setCoordinates(@Raw double xCoordinate, @Raw double yCoordinate)throws IllegalArgumentException{
		coordinateVector = new Vector(xCoordinate,yCoordinate);
	}

	/**
	 * Checks whether the given coordinate is valid.
	 *
	 * @param 	  coordinate
	 * 		  	  The coordinate to check.
	 * @return    Returns true if the given coordinate is not infinite or max values. 
	 * 			| result == ((coordinate != Double.NEGATIVE_INFINITY) && (coordinate != Double.POSITIVE_INFINITY) )
	 */
	public static boolean isValidCoordinate(double coordinate){
		if(Double.isNaN(coordinate)){
			return false;
		}
		return ((coordinate != Double.NEGATIVE_INFINITY) && (coordinate != Double.POSITIVE_INFINITY) );
	}
	




	//////////////
	// VELOCITY //
	//////////////

	private Vector velocityVector;
	private double maxSpeed;
	public static final double SPEED_OF_LIGHT = 300000;
	
	/**
	 * Sets the max speed for this entity
	 * @param maxSpeed
	 * 		  The new max speed to be set for this entity.
	 * @post ...
	 * 		| if((maxSpeed > 0 && maxSpeed <= SPEED_OF_LIGHT))
	 * 		| then new.getMaxSpeed() == maxSpeed
	 * 		| else if(maxSpeed <0)
	 * 		|    then new.getMaxSpeed() == getMaxSpeed()
	 *		| else new.getMaxSpeed() == SPEED_OF_LIGHT
	 */
	private void setMaxSpeed(double maxSpeed){
		 if(maxSpeed > 0 && maxSpeed <= SPEED_OF_LIGHT)
			 	this.maxSpeed = maxSpeed;
		 else if(maxSpeed < 0){}
		 else
			 this.maxSpeed = SPEED_OF_LIGHT;
	}

	/**
	 * 
	 * Returns the maximum speed of this entity.
	 * 
	 */
	@Basic
	public double getMaxSpeed(){
		return maxSpeed;
	}

	/**
	 * Returns the velocity of this entity according to the x-axis in km/s.
	 *
	 */
	@Basic 
	public double getXVelocity() {
		return velocityVector.getXComponent();
	}

	/**
	 * Returns the velocity of this entity according to the y-axis in km/s.
	 *
	 */
	@Basic
	public double getYVelocity() {
		return velocityVector.getYComponent();
	}

	/**
	 * Sets the velocities according to the x- and y-axis of this entity to the given value.
	 *
	 * @param 	xVelocity
	 * 			The new value of velocity according to the x-axis.
	 * @param 	yVelocity
	 * 			The new value of velocity according to the y-axis.
	 * @post    The new velocity is equals to the given value.
	 *		  | (new this).getXVelocity() == xVelocity
	 *		  | (new this).getYVelocity() == yVelocity
	 */		
	@Raw
	private void setVelocities(@Raw double xVelocity,@Raw double yVelocity){
		velocityVector = new Vector(xVelocity,yVelocity);
	}

	/**
	 * Checks whether the given velocity is valid.
	 * 
	 * @param 	  	  velocity
	 * 				  The velocity to be checked.
	 * @return		  Returns true if the given total velocity is greater than 0 and smaller than getMaxSpeed()
	 * 				| result == (velocity < -1*getMaxSpeed() || velocity > getMaxSpeed())
	 */
	public boolean isValidVelocity(double velocity){
		if(Double.isNaN(velocity))
			return false;
		if (velocity < -1*getMaxSpeed() || velocity > getMaxSpeed())
			return false;
		return true;
	}

	/**
	 * Sets the velocity of this entity to the given velocities if they are valid, otherwise they are modified to fit the criteria.
	 * 
	 * @param     xVelocity
	 * 		      The new velocity according to the x-axis of this entity.
	 * @param     yVelocity
	 * 		      The new velocity according to the y-axis of this entity.
	 * @post	  If the given xVelocity is not a number then it is equal to 0.
	 * 			| if(Double.isNaN(xVelocity))
	 * 			|	then (new this).getXVelocity() == 0
	 * @post	  If the given yVelocity is not a number then it is equal to 0.
	 * 			| if(Double.isNaN(yVelocity))
	 * 			|	then (new this).getYVelocity() == 0
	 * @post	  If one of the components is infinity and the other is finite, then the component having finite value is set to 0 
	 * 			  and the component with infinity is set to getMaxSpeed()
	 *  		| if( (Double.isInfinite(xVelocity))
	 *  		|		 && (!Double.isInfinite(yVelocity))
	 *  		|		then Math.abs((new this).getXVelocity()) == getMaxSpeed() && (new this).getYVelocity() == 0
	 *  		| if( (Double.isInfinite(yVelocity))
	 *  		|		 && (!Double.isInfinite(xVelocity))
	 *  		|		then Math.abs((new this).getYVelocity()) == getMaxSpeed() && (new this).getXVelocity() == 0
	 * @post      If the total velocity is smaller than or equal to getMaxSpeed()
	 * 			  then the velocity according to the x-axis is equal to the xVelocity
	 * 			  and the velocity according to the y-axis is equal to the yVelocity.
	 * 			| if((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2))) <= getMaxSpeed())
	 * 			|	then {(new this).getXVelocity() = xVelocity
	 * 			|		  (new this).getYVelocity() = yVelocity }
	 * @post	  If the total velocity exceeds getMaxSpeed() and either of the given velocities is equal to 0, 
	 * 			  then other velocity will be set to getMaxSpeed() taking their signs(positive or negative) into account. 
	 * 			| if ((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2))) > getMaxSpeed() && (xVelocity == 0 || yVelocity==0))
	 * 			|	then {	if(xVelocity == 0)
	 *			|				then { (new this).getXVelocity() = 0
	 *			|						if (yVelocity > 0)
	 *			|				        	then (new this).getYVelocity = getMaxSpeed() 
	 *			|						else
	 *			|							then (new this).getYVelocity = getMaxSpeed() * -1
	 *			|					 }	
	 *			|			else {
	 *			|					(new this).getYVelocity() = 0
	 *			|
	 *			|					if(xVelocity > 0)
	 *			|						then (new this).getXVelocity = getMaxSpeed()
	 *			|					else
	 *			|					    (new this).getXVelocity = getMaxSpeed() * -1
	 *			|
	 *			|			    }
	 *			|	}
	 *
	 * @post      If the total velocity exceeds getMaxSpeed() but none of the components are equal to 0 then
	 * 			  each component is modified so that the total velocity is equal to getMaxSpeed() but the direction
	 *            of the total velocity remains the same using method transformVelocities().
	 *          | if ((Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2))) > getMaxSpeed() && (xVelocity != 0 && yVelocity !=0){  
	 *          | (new this).getXVelocity() = Vector.transformVector(new Vector(xVelocity,yVelocity),getMaxSpeed()).getXComponent()
	 *          | (new this).getYVelocity()	= Vector.transformVector(new Vector(xVelocity,yVelocity),getMaxSpeed()).getYComponent()					
	 */
	public void setVelocity(double xVelocity, double yVelocity){
		double[] vel = convertToValidVelocity(xVelocity,yVelocity);
		xVelocity = vel[0];
		yVelocity = vel[1];
		
		double totalVelocity = (Math.sqrt(Math.pow(xVelocity, 2)+Math.pow(yVelocity,2)));
		if(totalVelocity <= getMaxSpeed()){
			setVelocities(xVelocity,yVelocity);
		}
		else if(xVelocity == 0 || yVelocity==0){
			if(xVelocity == 0){
				if(yVelocity < 0)
					setVelocities(0,-1 *getMaxSpeed());
				else
					setVelocities(0,getMaxSpeed());
			}
			else{
				if(xVelocity >= 0)
					setVelocities(getMaxSpeed(),0);
				else
					setVelocities(getMaxSpeed() * -1 , 0);
			}
		}
		else {
			Vector vector = Vector.transformVector(new Vector(xVelocity,yVelocity),getMaxSpeed());
			setVelocities(vector.getXComponent(),vector.getYComponent());
		}
	}


	private double[] convertToValidVelocity(double xVelocity,double yVelocity){
			double velocities[] = { xVelocity, yVelocity };
			if(Double.isNaN(xVelocity))
				velocities[0] = 0;
			if(Double.isNaN(yVelocity))
				velocities[1] = 0;
			if(Double.isInfinite(xVelocity) && !Double.isInfinite(yVelocity)){
				velocities[1]=0;
				if(xVelocity == Double.POSITIVE_INFINITY)
					velocities[0] = getMaxSpeed();
				else
					velocities[0] = -1* getMaxSpeed();
			}
			if(Double.isInfinite(yVelocity) && !Double.isInfinite(xVelocity)){
				velocities[0] = 0;
				if(yVelocity == Double.POSITIVE_INFINITY)
					velocities[1] = getMaxSpeed();
				else
					velocities[1] = -1* getMaxSpeed();
			}
			if(Double.isInfinite(xVelocity) && Double.isInfinite(yVelocity)){
				if(xVelocity == Double.POSITIVE_INFINITY)
					velocities[0] = getMaxSpeed();
				else
					velocities[0] = -1* getMaxSpeed();
				if(yVelocity == Double.POSITIVE_INFINITY)
					velocities[1] = getMaxSpeed();
				else
					velocities[1] = -1* getMaxSpeed();
			}
		
			return velocities;
		}
	
	/**
	 * Returns the total velocity of this entity.
	 * @return   The total velocity using pythagoras theorem.
	 * 			|  result == (Math.sqrt(Math.pow(getXVelocity(), 2)+Math.pow(getYVelocity(),2)))
	 * 			
	 */
	public double getTotalVelocity(){
		return velocityVector.getLength();
	}



	////////////
	// RADIUS //
	////////////

	private final double radius;
	private static final double minRadius =0;
	/**
	 * Returns the radius of this circular entity in km.
	 * 
	 */
	@Basic @Immutable
	public double getRadius() {
		return radius;		
	}

	/**
	 * Checks whether the given radius is valid.
	 * 
	 * @param 	 radius
	 * 			 The radius that has to be checked.		
	 * @return	 Returns true if the radius is not equal to infinity or max values and is larger than getMinRadius().
	 * 			| result == ( (radius != Double.MAX_VALUE) && (radius != Double.MIN_VALUE) 
	 *			| && (radius != Double.NEGATIVE_INFINITY) && (radius != Double.POSITIVE_INFINITY) 
	 *			| && (radius > getMinRadius() ))
	 */
	public static boolean isValidRadius(double radius){
		if(Double.isNaN(radius))
			return false;
		return ( (radius != Double.MAX_VALUE) && (radius != Double.MIN_VALUE) 
				&& (radius != Double.NEGATIVE_INFINITY) && (radius != Double.POSITIVE_INFINITY) 
				&& (radius > getMinRadius()));
	} 
	
	/**
	 * Returns the minimum allowed radius.
	 * 
	 */
	@Basic
	public static double getMinRadius(){
		return minRadius;
	}



	//////////////
	// MOVEMENT //
	//////////////

	/**
	 * Changes the position of this entity based on the current position, velocity and a given time duration.
	 * 
	 * @param 	duration 
	 * 			The duration for which the entity is in movement.
	 * @post    The new position of this entity is equal to sum of the current position with the product of the given duration 
	 * 			and the current velocity for each direction.  
	 * 		   | new.getXCoordinate() == getXVelocity()*duration + getXCoordinate() && 
	 * 		   | new.getYCoordinate() == getYVelocity()*duration + getYCoordinate()
	 * @throws 	IllegalArgumentException
	 * 			The given duration is invalid.
	 * 		   | ! isValidDuration (duration)	
	 */
	public void move(double duration)throws IllegalArgumentException{
		if(! isValidDuration(duration)){
			throw new IllegalArgumentException("Invalid duration");}
		setCoordinates(getXVelocity()*duration + getXCoordinate(),(getYVelocity()*duration + getYCoordinate()));
	}

	/**
	 * Checks whether the given duration is valid.
	 * 
	 * @param	  duration
	 * 		  	  The duration that has to be checked.
	 * @return 	  True if the given duration is greater or equal to 0.
	 * 		 	| result == ( (duration != Double.NEGATIVE_INFINITY) && (duration != Double.POSITIVE_INFINITY) 
	 *			|  && (duration >= 0 ))
	 */
	public static boolean isValidDuration(double duration){
		if(Double.isNaN(duration))
			return false;
		return ( (duration != Double.NEGATIVE_INFINITY) && (duration != Double.POSITIVE_INFINITY) 
				  && (duration >= 0 ));
	}


	
	
	/**
	 * Return the distance between this entity and the other entity, if both
	 * would move during the given time.
	 * 
	 * @param  other
	 *         The other entity to compare with.
	 * @param  moveTime
	 * 	       The time to move.
	 * @return If the given entity is this then result is equal to 0. Otherwise result is the distance between this and other entity, and is calculated using methods 
	 * 		   getPositionAfterMove(moveTime) and distanceTo method from vector class.
	 * 		 | if(other == this) then result == 0
	 * 		 | else
	 *       | Util.fuzzyEquals(result,
	 *       |         this.getPositionAfterMove(moveTime).distanceTo(other.getPositionAfterMove(moveTime))
	 *       |      - (this.getRadius() + other.getRadius())
	 * @throws IllegalArgumentException
	 *         Throws illegal argument exception if the given entity or duration is not valid.
	 *       | !isValidEntity(other)|| !isValidDuration
	 */
	public double distanceBetween(Entity other, double moveTime) throws IllegalArgumentException {
		if (!isValidDuration(moveTime))
			throw new IllegalArgumentException();
		if(other == this ) return 0;
		return this.getPositionAfterMove(moveTime).distanceTo(other.getPositionAfterMove(moveTime))
				   - (this.getRadius() + other.getRadius());
	}

	
	/**
	 * Return the distance between this entity and the other entity.
	 * 
	 * @param  other
	 *         The other entity to compare with.
	 * @return Returns the distance between this entity and the given entity.
	 *       | result == this.distanceBetween(other,0.0)
	 * @throws IllegalArgumentException
	 *         Throws illegal argument exception if the given entity is null.
	 *       | other == null
	 */
	public double getDistanceBetween(Entity other) throws IllegalArgumentException {
		if(other == null)
			throw new IllegalArgumentException("Invalid entity");
		return this.distanceBetween(other,0.0);
	}
	
	
	/**
	 * 
	 * Returns the position of the center of the this entity,after moving the given time.
	 * 
	 * @param 		  duration
	 * 				  The time during which this entity should be moved.
	 * @pre 		  duration should be valid.
	 * 				| isValidDuration(duration) 
	 * @return		  Returns the position vector in which this entity will be situated.
	 * 				| result.getXComponent ==  this.getXCoordinate() + duration*this.getXVelocity() 
	 * 				| result.getYComponent ==  this.getYCoordinate() + duration*this.getYVelocity()
	 */
	public  Vector getPositionAfterMove(double duration){
		assert(isValidDuration(duration));
		Vector position = new Vector(this.getXCoordinate() + duration*this.getXVelocity() , this.getYCoordinate() + duration*this.getYVelocity());
		return position;
	}
	
	
	
	/**
	 * 
	 * Checks if the given entity and this entity overlap.
	 * 
	 * @param 		  entity
	 * 				  The entity with which this entity should be compared to see if they overlap. 
	 * @return		  Returns true if this entity overlaps with the given entity or if the given entity is 
	 * 				  equal to this entity.
	 * 				| result == (getDistanceBetween(entity) < 0) || (this == entity)
	 * @throws		  IllegalArgumentException
	 * 				  Throws IllegalArgumentException if the given entity is invalid.	
	 * 				| (!isValidEntity(entity)) 	
	 */
	public boolean overlap(Entity entity) throws IllegalArgumentException{
		if(!isValidEntity(entity))
			throw new IllegalArgumentException("Invalid Entity, NullPointer or the given entity is terminated.");
		if(entity == this)
			return true;
		if(Util.fuzzyEquals(getDistanceBetween(entity), 0))
			return false;
		if(getDistanceBetween(entity) < 0)
			return true;
		else 
			return false;
		
	}

	
	/**
	 * 
	 * Checks whether the given entity is valid.
	 * 
	 * @param 		  entity
	 * 				  The entity that has to be checked.
	 * 
	 * @return		  Returns false if the given entity is null.
	 * 				| result == (entity != null)
	 * 
	 */
	public static boolean isValidEntity(Entity entity){
		if(entity == null || entity.isTerminated())
			return false;
		return true;
	}



	
	/////////////////
	// TERMINATION //
	/////////////////
	
	private boolean isTerminated = false;

	/**
	 * Check whether this entity is terminated.
	 */
	@Basic
	public boolean isTerminated() {
		return isTerminated;
	}

	/**
	 * Terminate this entity.
	 *
	 * @effect  The world, if any, is unset from this entity.
	 *        | unsetWorld()
	 * @post   This entity is terminated.
	 *       | new.isTerminated()
	 */
	protected void terminate() {
		unsetWorld();
		this.isTerminated= true;
	}
	
	
	
	
	
	///////////
	// WORLD //
	///////////
	
	private World world;

	
	/**
	 * Return the world of this entity.
	 *   A null reference is returned if this entity has no world.
	 */
	@Basic
	@Raw
	public World getWorld() {
		return this.world;
	}

	/**
	 * Check whether this entity can have the given world
	 * as its world.
	 *
	 * @param   world
	 *          The world to check.
	 * @return  If this entity is terminated, true if and only if
	 *          the given world is not effective.
	 *        | if (this.isTerminated())
	 *        |   then result == (world == null)
	 *          Otherwise, true if and only if the given world is
	 *          either not effective or not terminated.
	 *        | else result ==
	 *        |   (world == null) || (! world.isTerminated())
	 */
	@Raw
	public boolean canHaveAsWorld(World world) {
		if (this.isTerminated())
			return (world == null);
		return (world == null) || (!world.isTerminated());
	}

	/**
	 * Check whether this entity has a proper world.
	 *
	 * @return  True if and only if this entity can have its world as
	 *          its world, and if either the world of this entity is not
	 *          effective or that world has this entity as one of its
	 *          entities.
	 *        | result ==
	 *        |   canHaveAsWorld(getWorld() &&
	 *        |   ( (getWorld() == null) ||
	 *        |     getWorld().contains(this) )
	 */
	@Raw
	public boolean hasProperWorld() {
		return canHaveAsWorld(getWorld())
				&& ((getWorld() == null) || getWorld()
						.contains(this));
	}

	/**
	 * Check whether this entity has an world.
	 *
	 * @return  True if and only if the world of this entity is effective.
	 *        | result == (getWorld() != null)
	 */
	@Raw
	public boolean hasWorld() {
		return getWorld() != null;
	}

	/**
	 * Set the world of this entity to the given world.
	 *
	 * @param   world
	 *          The new world for this entity.
	 * @pre     The given world must already contain this entity.
	 * 		  | world.contains(this)
	 * @post    The world of this entity is the same as the given world.
	 *        | new.getWorld() == world
	 * @post    The given world contains this entity.
	 *        | (new world).contains(this)
	 * @throws  IllegalArgumentException
	 *          The given world is not effective or it cannot contain this entity.
	 *        | (world == null) ||
	 *        |   (! world.canHaveAsEntity(this))
	 * @throws  IllegalStateException
	 *          This entity already has a world.
	 *        | hasWorld()
	 */
	public void setWorldTo(@Raw World world)
		throws IllegalArgumentException, IllegalStateException {
		assert(world.contains(this));
		if ((world == null)
				|| (! world.canHaveAsEntity(this)))
			throw new IllegalArgumentException("Invalid World or world cannot have this as entity");
		if (this.hasWorld())
			throw new IllegalStateException("Already has world!");
		setWorld(world);
	}

	/**
	 * Unset the world, if any, from this entity.
	 *
	 * @post    This entity no longer has a world.
	 *        | ! new.hasWorld()
	 * @post    The former world of this entity, if any, no longer
	 *          has this entity as one of its entities.
	 *        |    (getWorld() == null)
	 *        | || (! (new getWorld()).contains(this))
	 */
	public void unsetWorld() {
		if (hasWorld()) {
			World formerWorld = this.getWorld();
			setWorld(null);
			formerWorld.removeAsEntity(this);
		}
	}

	/**
	 * Set the world of this entity to the given world.
	 *
	 * @param   world
	 *          The new world for this entity.
	 * @post    The world of this entity is the same as the given world.
	 *        | new.getWorld() == world
	 */
	@Raw
	private void setWorld(@Raw World world) {
		this.world = world;
	}

	/**
	 * Terminate this entity.
	 *
	 * @effect  The world, if any, is unset from this entity.
	 *        | unsetWorld()
	 * @post   This entity is terminated.
	 *       | new.isTerminated()
	 */
	public void die(){this.terminate();}
	
	
	////////////////////////////////////////////////////////////////////////////
	//								Collision   							  //
	////////////////////////////////////////////////////////////////////////////
	
	/**
	 * Checks whether the given entity is related with this entity.
	 * @param entity
	 * 			| The entity to check the relation with.
	 */
	public abstract boolean isRelated(Entity entity);
	
	/**
	 * Resolves the boundary collision of this entity.
	 * @param collisionListener
	 * 			The collision listener that has to be called. 
	 * @post    If this collides with right or left boundaries of its world then its velocity according to the x-axis is reversed.
	 * 			| if(!isTerminated() && ((Util.fuzzyEquals(this.getXCoordinate() + this.getRadius(),this.getWorld().getWidth()))	
	 *		    |    || (Util.fuzzyEquals(this.getXCoordinate() - this.getRadius(), 0))))
	 *		    | then 
	 *		    |    (new this).getXVelocity()== -1 * this.getXVelocity() 
	 *			|     && (new this).getYVelocity() == this.getYVelocity()
	 * @post 	If this collides with upper or lower boundaries of its world then its velocity according to the y-axis is reversed.
	 * 			| if(!isTerminated() && ((Util.fuzzyEquals(this.getYCoordinate() + this.getRadius(),this.getWorld().getHeight()))	
	 *		    |    || (Util.fuzzyEquals(this.getYCoordinate() - this.getRadius(), 0))))
	 *		    | then 
	 *		    |    (new this).getYVelocity()== -1 * this.getYVelocity() 
	 *			|     && (new this).getXVelocity() == this.getXVelocity()
	 *
	 */
	public void collideWithBoundaries( CollisionListener collisionListener) {
		assert (Entity.isValidEntity(this));
		double[] boundaryPosition = new double[2];
		
		if ((Util.fuzzyEquals(this.getXCoordinate() + this.getRadius(),this.getWorld().getWidth()))	
			|| (Util.fuzzyEquals(this.getXCoordinate() - this.getRadius(), 0))) {
				this.setVelocity(-1 * this.getXVelocity(),this.getYVelocity());
				boundaryPosition[0]= (this.getXCoordinate() + this.getRadius()) >= this.getWorld().getWidth() ? this.getWorld().getWidth() : 0;
				boundaryPosition[1]= this.getYCoordinate();
			
		}
		if ((Util.fuzzyEquals( this.getYCoordinate() + this.getRadius(),this.getWorld().getHeight()))
			|| (Util.fuzzyEquals(this.getYCoordinate() - this.getRadius(), 0) )) {
				this.setVelocity(this.getXVelocity(),-1 * this.getYVelocity());
				boundaryPosition[0]=this.getXCoordinate();
				boundaryPosition[1]=(this.getYCoordinate() + this.getRadius()) >= this.getWorld().getHeight() ? this.getWorld().getHeight() : 0 ;
				
		}
		if(collisionListener!=null){
		collisionListener.boundaryCollision(this, boundaryPosition[0], boundaryPosition[1]);
		}
	}
	
	/**
	 * Resolves the collision between this entity and the given entity.
	 * @param	 entity2
	 * 			| The entity with which the collision occurs.
	 * @param 	collisionListener
	 * 			|  The collision listener that has to be called.
	 * @effect   If this canBounceWith(entity2) then collideBounce(entity2)
	 * 			|if(canBounceWith(entity2))
	 * 			| then this.collideBounce(entity2)
	 */
	public abstract void collide(Entity entity2, CollisionListener collisionListener);
	
	/**
	 * Checks whether the given entity is able to bounce with this entity.
	 * @param entity1
	 * 			The entity to be checked with.
	 * @return	Return true if both the entities are of class Asteroid or both of class Ship.	
	 */
	public abstract boolean canBounceWith(Entity entity1);
	
	
	/**
	 * Resolves the bouncing collision between this and the given entity.
	 * @param other
	 * 		    The other entity of this bouncing collision.
	 * @pre     The given entities should be able to bounce.
	 * 		  |  canBounceWith(other)
	 * 
	 * @post    The total kinetic energy of the colliding entities remains the same.
	 * 		  |  let voor be this.getMass()*Math.pow(this.getTotalVelocity(),2)/2 + other.getMass()*Math.pow(other.getTotalVelocity(),2)/2
	 *		  |	    & na be (new this).getMass()*Math.pow((new this).getTotalVelocity(),2)/2 + (new other).getMass()*Math.pow((new other).getTotalVelocity(),2)/2
	 *		  |  Math.abs(na-voor) < 10^-8
	 * @post    After bouncing this entity will not collide with other.
	 *		  |  getTimeToCollision((new other)) == Double.POSITIVE_INFINITY
	 * @post    ...
	 * 		  |  let  mass1 be this.getMass()
	 *	 	  |		  mass2 be other.getMass()
	 *	 	  |		  deltaX be other.getXCoordinate() - this.getXCoordinate()
	 *	 	  |		  deltaY be other.getYCoordinate() - this.getYCoordinate()
	 *	 	  |		  distance be this.getRadius()+other.getRadius()
	 *		  |		  Vector deltaR be new Vector(deltaX, deltaY)
	 *		  |		  Vector deltaV be new Vector(other.getXVelocity()
	 *		  |							- this.getXVelocity(), other.getYVelocity()
	 *		  |							- this.getYVelocity())
	 *	 	  |		  deltaVR be Vector.dotProduct(deltaV, deltaR)
	 *		  |		  parameterJ be 2 * mass2 * mass1 * deltaVR
	 *	 	  |				/ (distance * (mass1 + mass2))
	 *		  |		  parameterJx be parameterJ * deltaX / distance
	 *		  |		  parameterJy be parameterJ * deltaY / distance
	 *		  | then 
	 * 		  | (new this).getXVelocity() == this.getXVelocity() + parameterJx / mass1 &&
	 *		  | (new this).getYVelocity() == this.getYVelocity() + parameterJy / mass1 &&
	 *	      | (new other).getXVelocity() == other.getXVelocity() - parameterJx / mass2 &&
	 *		  |	(new other).getYVelocity() == other.getYVelocity() - parameterJy / mass2)
	 *
	 */
	public void collideBounce(Entity other) {
		assert(canBounceWith(other));
		double massi = this.getMass();
		double massj = other.getMass();
		double deltaX = other.getXCoordinate() - this.getXCoordinate();
		double deltaY = other.getYCoordinate() - this.getYCoordinate();
		double distance = this.getRadius()+other.getRadius();
		Vector deltaR = new Vector(deltaX, deltaY);
		Vector deltaV = new Vector(other.getXVelocity()
				- this.getXVelocity(), other.getYVelocity()
				- this.getYVelocity());
		double deltaVR = Vector.dotProduct(deltaV, deltaR);
		double parameterJ = 2 * massj * massi * deltaVR
				/ (distance * (massi + massj));
		double parameterJx = parameterJ * deltaX / distance;
		double parameterJy = parameterJ * deltaY / distance;
		this.setVelocity(this.getXVelocity() + parameterJx / massi,
				this.getYVelocity() + parameterJy / massi);
		other.setVelocity(other.getXVelocity() - parameterJx / massj,
				other.getYVelocity() - parameterJy / massj);	
	}
	
	/**
	 * 
	 * Calculates the collision position between the given other entity and this entity.
	 * 
	 * @param 		other
	 * 				The other entity with which the collision position is to be calculated.
	 * 
	 * @return		Returns the collision position as an array of doubles with an x- and y-coordinate, if other entity and this entity ever collide, otherwise null.
	 * 		  	  | if(getTimeToCollision(this,other) == Double.POSITIVE_INFINITY
	 * 			  |		then result == null
	 *			  |	else 
	 * 			  |		let
	 * 			  |			colPosEntity1 = {other.getPositionAfterMove(collisionTime).getXComponent(),other.getPositionAfterMove(collisionTime).getYComponent()};
	 *			  |			colPosEntity = {(this.getPositionAfterMove(collisionTime).getXComponent()),this.getPositionAfterMove(collisionTime).getYComponent()};
	 * 			  |			angle = Math.atan((colPosEntity[1]- colPosEntity1[1]) / (colPosEntity[0]- colPosEntity1[0]))
	 * 			  |		in
	 * 			  |			if(angle != 0){
	 * 			  |				if((colPosPosEntity[0]- colPosEntity1[0]) < 0){
	 *			  |					result[0] == (colPosEntity1[0] - Math.cos(angle) * other.getRadius());
	 *			  |					result[1] == (colPosEntity1[1]- Math.sin(angle) * other.getRadius());
	 *			  |				}
	 *			  |				else {
	 *			  |					result[0] == (colPosEntity1[0] + Math.cos(angle) * other.getRadius());
	 *			  |					result[1] == (colPosEntity1[1] + Math.sin(angle) * other.getRadius());
	 *			  |				}
	 *			  |			}
	 *			  |			else{
	 *			  |				if(colPosEntity1[1] < colPosEntity[1]){
	 *			  |					result[0] == (colPosEntity1[0]);
	 *			  |					result[1] == (colPosEntity1[1] + other.getRadius());
	 *			  |				}
	 *			  |				else {
	 *			  |					result[0] == (colPosEntity1[0]);
	 *			  |					result[1] == (colPosEntity1[1] - other.getRadius());
	 *			  |				}
	 *			  |			}
	 * @throws 		IllegalArgumentException
	 * 				Throws IllegalArgumentException if the other entity is invalid.
	 * 			  | !isValidEntity(other)
	 */		
	public double[] getCollisionPosition(Entity other) throws IllegalArgumentException {
	if(!Entity.isValidEntity(other) ){
		throw new IllegalArgumentException("Invalid Entity");
		}
		double collisionTime = getTimeToCollision(other);
		if(collisionTime == Double.POSITIVE_INFINITY )
			return null;
		double[] colThis = {other.getPositionAfterMove(collisionTime).getXComponent(),other.getPositionAfterMove(collisionTime).getYComponent()};
		double[] colEntity = {(this.getPositionAfterMove(collisionTime).getXComponent()),this.getPositionAfterMove(collisionTime).getYComponent()};
		if ( colThis[1] == colEntity[1]){
			double[] collisionPosition = new double[2];
			if(colEntity[0] > colThis[0]){
				collisionPosition[0] = (colThis[0]+other.getRadius());
				collisionPosition[1] = colThis[1];
			}
			else{
				collisionPosition[0] = (colThis[0]-other.getRadius());
				collisionPosition[1] = colThis[1];
			}
			return collisionPosition;
		}
		double angle =  Math.atan((colEntity[1]-colThis[1])/(colEntity[0]- colThis[0]));
		double[] collisionPosition = new double[2];
		if (angle != 0) {
			double xChange = Math.cos(angle) * other.getRadius();
			double yChange = Math.sin(angle) * other.getRadius() ;
			if((colEntity[0]- colThis[0]) < 0){
				collisionPosition[0] = (colThis[0] - xChange);
				collisionPosition[1] = (colThis[1] - yChange);
			}
			else {
				collisionPosition[0] = (colThis[0] + xChange);
				collisionPosition[1] = (colThis[1] + yChange);
			}
		}
		else {
			if(colThis[1] < colEntity[1]){
				collisionPosition[0] = (colThis[0]);
				collisionPosition[1] = (colThis[1] + other.getRadius());
			}
			else {
				collisionPosition[0] = (colThis[0]);
				collisionPosition[1] = (colThis[1] - other.getRadius());
			}
		}
		
	return collisionPosition;
	}
	
	/**
	 * Returns the time it will take for this entity to collide with other.
	 * @param 	other
	 * 			The entity to calculate the collision time with.
	 * @return  The resulting time is not negative and different from Double.NaN
	 *        | Util.fuzzyLeq(0.0,result) && (! Double.isNaN(result))
	 * @return  If the resulting time is finite, the distance between both
	 *          entities would be fuzzy equal to zero if they would both move
	 *          during the resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |	  this.move(result) && other.move(result)
	 *        |   Util.fuzzyEquals((new this).distanceBetween((new other)),0.0)
	 * @return  If the resulting distance is finite, the distance between both entities
	 *          would be fuzzy different from zero if they would move for a time shorter than the
	 *          resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |   for each time in 0.0..result:
	 *        |     if (time < result)
	 *        |       then	
	 *        				! Util.fuzzyEquals(this.distanceBetween(other,time),0.0)
	 * @return  If the resulting time is infinite, this ship is the same as the
	 *          other ship or the distance between both
	 *          ships would be different from zero for each finite time they would move.
	 *        | if (result == Double.POSITIVE_INFINITY) then
	 *        |   (this == other) ||
	 *        |   (for each time in 0.0..Double.POSITIVE_INFINITY:
	 *        |     if (! Double.isInfinite(time)) then
	 *        |       (! Util.fuzzyEquals(this.distanceBetween(other,time),0.0))
	 * @throws  IllegalArgumentException
	 *          The other entity is not valid.
	 *        |  !Entity.isValidEntity(other)
	 */
	public double getTimeToCollision(Entity other) throws IllegalArgumentException{
		if( !Entity.isValidEntity(other))
			throw new IllegalArgumentException("Invalid Entity, NullPointer");
		Vector deltaR = new Vector(this.getXCoordinate()- other.getXCoordinate(),this.getYCoordinate() - other.getYCoordinate()); 
		Vector deltaV =  new Vector(this.getXVelocity() - other.getXVelocity(), this.getYVelocity() - other.getYVelocity());
		double deltaRR = Vector.dotProduct(deltaR,deltaR);
		double deltaVV = Vector.dotProduct(deltaV,deltaV);
		double deltaVR = Vector.dotProduct(deltaV,deltaR);
		double parameterD = Math.pow(deltaVR,2) - (deltaVV)*(deltaRR - Math.pow((other.getRadius()+this.getRadius()),2));  
		if(parameterD <= 0 || deltaVR >=0)
			return Double.POSITIVE_INFINITY;
		else 
			return - ((deltaVR + Math.sqrt(parameterD))/deltaVV);	
	}
	
	/**
	 * Returns the smallest time in which this entity will collide with the boundaries of its world.
	 *  
	 * @pre 	The world of this entity should not be null.
	 * 		  | this.getWorld()!=null
	 * @return  The resulting time is not negative and different from Double.NaN
	 *        | Util.fuzzyLeq(0.0,result) && (! Double.isNaN(result))
	 * @return  If the resulting time is finite, the distance between this entity
	 *          with one of the boundaries of its world would be fuzzy equal to zero if this entity moved
	 *          the resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |	  this.move(result)
	 *        |   (Util.fuzzyEquals((new this).getXCoordinate() + (new this).getRadius(),this.getWorld().getWidth()))
	 *        |	  || (Util.fuzzyEquals((new this).getXCoordinate() - (new this).getRadius(), 0) ||
	 *        |   (Util.fuzzyEquals( (new this).getYCoordinate() + (new this).getRadius(),(new this).getWorld().getHeight()))
	 *		  |	  || (Util.fuzzyEquals((new this).getYCoordinate() - (new this).getRadius(), 0) )
	 * @return  If the resulting distance is finite, the distance between both this entity and the world boundaries
	 *          would be fuzzy different from zero if this entity would move for a time shorter than the
	 *          resulting time.
	 *        | if (result < Double.POSITIVE_INFINITY) then
	 *        |   for each time in 0.0..result:
	 *        |     if (time < result)
	 *        |       then	
	 *        			   this.move(time) && (
	 *        |				  !(Util.fuzzyEquals((new this).getXCoordinate() + (new this).getRadius(),this.getWorld().getWidth()))
	 *        |			   && !(Util.fuzzyEquals((new this).getXCoordinate() - (new this).getRadius(), 0) &&
	 *        |   			  !(Util.fuzzyEquals( (new this).getYCoordinate() + (new this).getRadius(),(new this).getWorld().getHeight()))
	 *		  |	  		   && !(Util.fuzzyEquals((new this).getYCoordinate() - (new this).getRadius(), 0) )
	 */
	public  double getTimeToBoundaryCollision () throws IllegalArgumentException{
		assert(this.getWorld()!=null);
		double time = Double.POSITIVE_INFINITY;
		if(this.getXVelocity()<0){
			time = Math.abs((this.getXCoordinate()- (0+this.getRadius()))/this.getXVelocity());
	
		}
		if(this.getXVelocity()>0){
			time = (this.getWorld().getWidth() - (this.getXCoordinate()+this.getRadius()))/this.getXVelocity();
		
		}
		if(this.getYVelocity()<0){
			double timey = Math.abs((this.getYCoordinate()- (0+this.getRadius()))/this.getYVelocity());
			if(time > timey)
				time = timey;
		}
		if(this.getYVelocity()>0){
			double timey = (this.getWorld().getHeight() - (this.getYCoordinate()+this.getRadius()))/this.getYVelocity();
			if(time > timey)
				time = timey;
		}
		return (time);
	}
}
	



	


